/*
 *  jOTR - Java Off-The-Record Messaging Library
 *  Copyright (C) 2007 Markus Karnik
 *
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, 
 *  or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along 
 *  with this program; if not, see <http://www.gnu.org/licenses/>.
 *  
 */

package de.karnik.libjotr;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Vector;

import javax.crypto.Cipher;

import de.karnik.helper.ArrayFunctions;
import de.karnik.helper.HashFunctions;
import de.karnik.helper.LogFunctions;
import de.karnik.helper.StringFunctions;
import de.karnik.libjotr.helper.JotrFunctions;

/**
 * Implements the basic OTR protocol.
 * 
 * @author Markus Karnik - markus.karnik@my-design.org
 * @version 1.0
 * @since 1.0
 */
public class Jotr implements JotrConstants {

	/**
	 * Contains the current listeners for this instance.
	 */
	private Vector<JotrListener> jotrListeners = new Vector<JotrListener>();
	
	/**
	 * The static Jotr singleton object.
	 */
	private static Jotr jotr = null;
	
	/**
	 * Returns the singleton Jotr object.
	 * 
	 * @return The singleton Jotr object.
	 */
	public static Jotr getInstace() {
		
		if( jotr == null )
			jotr = new Jotr();
		
		return jotr;
	}
	
	/**
	 * Invisible.
	 */
	private Jotr() {}
	
	// #############################################################################
	// Basic API Functions
	// #############################################################################
	
	/**
	 * Creates and returns a new JotrContext object with default options.
	 * 
	 * @return A new JotrContext object. 
	 * 
	 * @throws NoSuchAlgorithmException 
	 * @throws InvalidAlgorithmParameterException 
	 */
	public JotrContext createNewContext() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
		LogFunctions.log( "Jotr.createNewContext()", "new context to create" );
		return new JotrContext();
	}
	
	/**
	 * Appends an OTR query message.
	 * 
	 * @param context The JotrContext to use to create the auth message.
	 * @param userName Username to use in query message. If userName is <b>null</b> the username "Nobody" will be used.
	 * @param message The message to append the auth message to.
	 */
	public void generateQueryMessage( JotrContext context, String userName, StringBuffer message ) {
		
		// generate version string
		// "?OTR?" 
		//    Version 1 only 
		// "?OTRv2?" 
		//    Version 2 only 
		// "?OTR?v2?" 
		//    Versions 1 and 2
		
		message.append( OTR_HEADER );
		
		if( context.isAllowV1() ) {
			message.append( '?' );
		
			if( context.isAllowV2() )
				message.append( 'v' ).append( PROTOCOL_VERSION_2 );
		
		} else {
			
			if( context.isAllowV2() )
				message.append( 'v' ).append( PROTOCOL_VERSION_2 );
			else
				message.append( 'v' );
			
		}
		
		message.append( '?' ).append( '\n' );
		
		// append message
		if( userName == null )
			message.append( "Nobody" );
		else
			message.append( userName );
				
		message.append( OTR_REQUEST_MESSAGE );
	}
	
	/**
	 * Creates an OTR protocol message. The message type depends on the type parameter.
	 * If type MESSAGE_TYPE_ERROR_MSG you can put an error message to the message buffer.
	 * 
	 * @param type The protocol message type to append.
	 * @param context The JotrContext to use to create the protocol message.
	 * @param message The message to append the protocol message to.
	 * 
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_DH_COMMIT_MSG
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_DH_KEY_MSG
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_REVEAL_SIG_MSG
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_SIG_MSG
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_QUERY_MSG
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_TYPE_ERROR_MSG
	 */
	public void createOTRProtoMessage( int type, JotrContext context, StringBuffer message, String userName ) {
				
		JotrDH jotrDh = context.getJotrDh();
		
		// if needed, create d-h keys
		if( jotrDh.getCurrentLocalDHKeyPair().getPrivateKey() == null ) {
			LogFunctions.log( "Jotr.createOutgoingOTRMessage", "compute dh keypair" );
		
			// create d-h keys
			try {
				jotrDh.computeFreshDiffieHellmanKeyPairs();
			} catch ( Exception e ) {
				LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getMessage() );
				LogFunctions.log( "Jotr.createOutgoingOTRMessage" , "Exception", e.getStackTrace() );
			}
		}
		
		// switch type and call needed function
		switch( type ) {
			case MESSAGE_TYPE_DH_COMMIT_MSG:
				
				// inform listeners
				for( int i = 0; i < this.jotrListeners.size(); i++ )
					this.jotrListeners.get( i ).authentificationStarted( context );
				
				generateDHCommitMessage( context, message );
				break;
				
			case MESSAGE_TYPE_DH_KEY_MSG:

				generateDHKeyMessage( context, message );
				break;
				
			case MESSAGE_TYPE_REVEAL_SIG_MSG:
				
				generateSignatureMessage( context, message, true );
				break;
				
			case MESSAGE_TYPE_SIG_MSG:
				
				generateSignatureMessage( context, message, false );
				break;
				
			case MESSAGE_TYPE_QUERY_MSG:
				
				generateQueryMessage( context, userName, message );
				break;
				
			case MESSAGE_TYPE_ERROR_MSG:
				JotrFunctions.injectOTRErrorMessage( message );
				break;
		}
		
	}
	
	/**
     * The function is used to process outgoing messages. Depending on the state saved in the context, 
     * the messages are either encrypted or sent in the clear. <br><br>
     * 
     * The functions processes the message (StringBuffer), and returns a result code to
     * indicate whether the message was encrypted, sent in the clear, should be ignored (a protocol 
     * message that does not carry user data), or if there was a protocol error. 

	 * 
	 * @param context The current JotrContext.
	 * @param message The Message to process.
	 * @return The message type.
	 * 
	 * @throws NoSuchAlgorithmException 
	 * @throws NoSuchProviderException 
	 * 
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_ENCRYPTED
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_ERROR
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_IGNORE
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_PLAIN
	 */
	public int processOutgoingMessage( JotrContext context, StringBuffer message ) throws NoSuchAlgorithmException, NoSuchProviderException {
		
		LogFunctions.log( "Jotr.processOutgoingMessage", "initial log message" );

		// process normal message
		if( context.getAuthstate() == JotrContext.AUTHSTATE_NONE ) {
			
			LogFunctions.log( "Jotr.processOutgoingMessage", "authstate = none" );

			switch( context.getMessageState() ) {
				case JotrContext.MSGSTATE_ENCRYPTED:
					createDataMessage( context, message );
					// TODO APPEND FOR TLV's
					return MESSAGE_ENCRYPTED;
				case JotrContext.MSGSTATE_FINISHED:
				case JotrContext.MSGSTATE_PLAINTEXT:
					JotrFunctions.injectWhitspaceTag( message );
					return MESSAGE_PLAIN;
				default:
					return MESSAGE_ERROR;
			}
		}
				
		return MESSAGE_IGNORE;
	}
	
	/**
     * The function is used to process incoming messages. Depending on the state saved in the context, 
     * the messages are either encrypted or sent in the clear.<br><br>
     * 
     * The functions processes the message (StringBuffer), and returns a result code to
     * indicate whether the message was encrypted, sent in the clear, should be ignored (a protocol 
     * message that does not carry user data), or if there was a protocol error. 
     * 
	 * @param context
	 * @param message
	 * @return Returns the message status.
	 * @throws InvalidKeySpecException 
	 * @throws NoSuchAlgorithmException 
	 * 
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_ENCRYPTED
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_ERROR
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_IGNORE
	 * @see de.karnik.libjotr.JotrConstants#MESSAGE_PLAIN
	 */
	public int processIncomingMessage( JotrContext context, StringBuffer message ) throws JotrProtocolException, NoSuchAlgorithmException, InvalidKeySpecException {
		
		LogFunctions.log( "Jotr.processIncomingMessage", "initial log message" );		
		JotrDH jotrDh = context.getJotrDh();
		
		
		// check for query message
		int bestVersion = 0;
		if( ( bestVersion = JotrFunctions.checkForOTRQueryHeader( message ) ) > 0 ) {

			// set message state
			context.setMessageState( JotrContext.MSGSTATE_PLAINTEXT );
			
			if( bestVersion == 1 ) {
				// TODO send v1 DH-Commit
			}
			
			context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_DHKEY );
			
			if( bestVersion == 2 ) {
				for( int i = 0; i < this.jotrListeners.size(); i++ )
					this.jotrListeners.get( i ).dhCommitMessageRequested( context );
			}
			
			return MESSAGE_IGNORE;
			
		}
		
		// check for if message is an error message
		if( JotrFunctions.checkForOTRErrorHeader( message ) )  {
			
			// set message state
			context.setMessageState( JotrContext.MSGSTATE_PLAINTEXT );
			
			// inform listeners
			for( int i = 0; i < this.jotrListeners.size(); i++ )
				this.jotrListeners.get( i ).errorMessageReceived( context, JotrFunctions.stripOtrErrorMessage( message ) );
			
			// start new ake if configured
			if( context.isErrorStartAke() )
				for( int i = 0; i < this.jotrListeners.size(); i++ )
					this.jotrListeners.get( i ).queryMessageRequested( context );
			
			return MESSAGE_IGNORE;
		}
		
		
		// if OTR message
		if( JotrFunctions.checkForOTRHeader( message ) ) {
			
			LogFunctions.log( "Jotr.processIncomingMessage", "otr header available" );

			// set message state
			context.setMessageState( JotrContext.MSGSTATE_ENCRYPTED );
			
			// strip otr header ( ?OTR: ) and footer (.)
			String tempMessage = JotrFunctions.stripOtrMessage( message );
			tempMessage = StringFunctions.decodeBase64( tempMessage );
			
			// check for TLV's
			// TODO check for TLV's
			
			// get bytes
			byte[] bytes = tempMessage.getBytes();
			
			// set byte array cursor to 0
			int cursor = 0;
			
			// test for supported versions
			if( JotrFunctions.isSupportedVersion( ( int )ArrayFunctions.getBigEndianValue( cursor, SHORT_BYTE_LENGTH, bytes ) ) ) {
				cursor += SHORT_BYTE_LENGTH;
				
				LogFunctions.log( "Jotr.processIncomingMessage", "supported otr version available" );
				
				// switch message types
				switch( ( int )ArrayFunctions.getBigEndianValue( cursor, BYTE_BYTE_LENGTH, bytes ) ) {
					
					// MESSAGE TYPE : Data Message
					case MESSAGE_TYPE_DATA_MSG:
						cursor += BYTE_BYTE_LENGTH;
						
						if( context.getMessageState() == JotrContext.MSGSTATE_ENCRYPTED ) {
							
							boolean ignore = false;
							
							try {
						
								LogFunctions.log( "Jotr.processIncomingMessage", "message type = MESSAGE_TYPE_DATA_MSG " );
							
								// get key pairs
								JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
								JotrDHKeyPair curLocalKeys = jotrDh.getCurrentLocalDHKeyPair();

								// get flags
								if( ArrayFunctions.getBigEndianValue( cursor, BYTE_BYTE_LENGTH, bytes ) == 1 )
									ignore = true;
								
								cursor += BYTE_BYTE_LENGTH;

								// get keys
								int senderKeyId = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
								cursor += INT_BYTE_LENGTH;
								int recipientKeyId = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
								cursor += INT_BYTE_LENGTH;
								
								// check keyid's
							    if ( jotrDh.getCurrentRemotePublicKeyId() == 0 
							    		|| ( senderKeyId != jotrDh.getCurrentRemotePublicKeyId() && senderKeyId != jotrDh.getPreviousRemotePublicKeyId() ) 
							    		|| ( recipientKeyId != curLocalKeys.getKeyID() && recipientKeyId != prevLocalKeys.getKeyID() ) 
							    		|| senderKeyId == 0 
							    		|| recipientKeyId == 0 ) {
							    	
							    	throw new JotrProtocolException( JotrProtocolException.UNREADABLE_MSG_RECV );
							    }
							    
							    // if old key is requested, check availability
							    if ( senderKeyId == jotrDh.getPreviousRemotePublicKeyId() && jotrDh.getPreviousRemotePublicKey() == null ) 
							    	throw new JotrProtocolException( JotrProtocolException.UNREADABLE_MSG_RECV );

							    // get correct sessionKeys
							    JotrSessionKeys sessKeys = jotrDh.getSessKeys()
							    							[ curLocalKeys.getKeyID() - recipientKeyId ]
							    							[ jotrDh.getCurrentRemotePublicKeyId() - senderKeyId];
							    
							    
							    
								// get new dh key
								byte[] nextDH = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, bytes );
								cursor += ( INT_BYTE_LENGTH + nextDH.length );
								
								// get counter
								byte[] ctr = ArrayFunctions.seperateDataFromArray( cursor, CTR_BYTE_LENGTH, bytes );
								cursor += CTR_BYTE_LENGTH;
								
								if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "ctr", ArrayFunctions.getBigEndianValue( 0, CTR_BYTE_LENGTH, ctr ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "getTheirAesCounter", context.getTheirAesCounter() );
								}
								// check counter value
								if( ArrayFunctions.getBigEndianValue( 0, CTR_BYTE_LENGTH, ctr ) <= context.getTheirAesCounter() )
									throw new JotrProtocolException( JotrProtocolException.UNREADABLE_MSG_RECV );
								
								//TODO check theire CTR 
								
								byte[] encryptedData = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, bytes );
								cursor += ( INT_BYTE_LENGTH + encryptedData.length );						
								
								//
								// verify integrity with the hmac
								//
								
								// get data and create hmac-sha1
								byte[] mac_check = ArrayFunctions.seperateDataFromArray( 0, cursor, bytes );
								mac_check = HashFunctions.hmacSha1( sessKeys.getReceivingMacKey(), mac_check );
								
								// get remote hmac
								byte[] hmac = ArrayFunctions.seperateDataFromArray( cursor, MAC_BYTE_LENGTH, bytes );
								cursor += MAC_BYTE_LENGTH;
							
								if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "mac_check", StringFunctions.bytesToHex( mac_check ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "hmac", StringFunctions.bytesToHex( hmac ) );
								}
								
								// compare
								if( !ArrayFunctions.compare( mac_check,  hmac ) )
									throw new JotrProtocolException( JotrProtocolException.UNREADABLE_MSG_RECV );
								
								// set mac recv mac keys as used
								sessKeys.setReceivingUsed( true );
								
								// get revealed macs ( ignore )
								byte[] revealMac = ArrayFunctions.seperateDataFromArray( cursor, ( bytes.length - cursor ), bytes ); 
								
								if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "senderKeyId", senderKeyId );
									LogFunctions.log( "Jotr.processIncomingMessage", "recipientKeyId", recipientKeyId );
									LogFunctions.log( "Jotr.processIncomingMessage", "ctr", StringFunctions.bytesToHex( ctr ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "nextDH", StringFunctions.bytesToHex( nextDH ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "revealMac", StringFunctions.bytesToHex( revealMac ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "complete", StringFunctions.bytesToHex( bytes ) );
								}
								
								// decrypt message
								Cipher c = JotrFunctions.generateDecryptingAesCipher( sessKeys.getReceivingAesKey() , ctr );
								byte[] decryptedData = c.doFinal( encryptedData );
								
								// set decrypted message
								message.setLength( 0 );
								message.append( new String( decryptedData ) );
								
							    // see if either set of keys needs rotating
							    if ( recipientKeyId == curLocalKeys.getKeyID() ) {
							    	// they're using our most recent key, so generate a new one
							    	jotrDh.rotateLocaleKeyPairs();
							    }

							    if ( senderKeyId == jotrDh.getCurrentRemotePublicKeyId() ) {
							    	/* They've sent us a new public key */
							    	jotrDh.rotateRemotePublicKey( nextDH );
							    }
							    
								return MESSAGE_ENCRYPTED;
							
							} catch( JotrProtocolException e ) {
								if( !ignore )	
									throw e;
							
								return MESSAGE_IGNORE;
								
							} catch( Exception e ) {

								LogFunctions.log( "Jotr.processIncomingMessage", "Exception", e.getMessage() );
								LogFunctions.log( "Jotr.processIncomingMessage", "Exception", e.getStackTrace() );
								
								if( !ignore )
									throw new JotrProtocolException( JotrProtocolException.MALFORMED_DATA_MSG_RECV );

								//	TODO nickname an other stuff
								
								return MESSAGE_IGNORE;
							}
						
						} else {
							throw new JotrProtocolException( JotrProtocolException.UNREADABLE_MSG_RECV );
						}
				
					// MESSAGE TYPE : D-H Commit Message	
					case MESSAGE_TYPE_DH_COMMIT_MSG:
						// increment array cursor
						cursor += BYTE_BYTE_LENGTH;
						
						LogFunctions.log( "Jotr.processIncomingMessage", "message type = MESSAGE_TYPE_DH_COMMIT_MSG " );
						
						// inform listeners
						for( int i = 0; i < this.jotrListeners.size(); i++ )
							this.jotrListeners.get( i ).dhCommitMessageReceived( context );
						
						//
						// If ALLOW_V2 is not set, ignore this message. Otherwise:
						//
						if( context.isAllowV2() ) {
							
							//
							// get message contents
							//
							
							// get encrypted remote public key length and increment array cursor
							int dataLength = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
							cursor += INT_BYTE_LENGTH;

							// get encrypted remote public key and increment array cursor
							byte[] gy_byteArray = ArrayFunctions.seperateDataFromArray( cursor, dataLength,  bytes );
							cursor += dataLength;
							
							LogFunctions.log( "Jotr.processIncomingMessage", "otr - gy_byteArray", 
											StringFunctions.bytesToHex( gy_byteArray ) );

							// get sha256 hash length and increment array cursor
							dataLength = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
							cursor += INT_BYTE_LENGTH;
							
							// get sha256 hash
							byte[] gy_hash = ArrayFunctions.seperateDataFromArray( cursor, dataLength,  bytes );
							
							LogFunctions.log( "Jotr.processIncomingMessage", "otr - gy_hash", 
							 					StringFunctions.bytesToHex( gy_hash ) );

							// Switch auth states
							switch( context.getAuthstate() ) {
							
								//
								// If authstate is AUTHSTATE_NONE:
							    // Reply with a D-H Key Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.
								//
								case JotrContext.AUTHSTATE_NONE:
									
									context.setRemoteAesEncryptedPublicKey( gy_byteArray );
									context.setRemotePublicKeyHash( gy_hash );
									
									context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_REVEALSIG );
									
									LogFunctions.log( "Jotr.processIncomingMessage", "compute dh keypair" );
									
									// create d-h keys
									try {
										jotrDh.computeFreshDiffieHellmanKeyPairs();
									} catch ( Exception e ) {
										LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getMessage() );
										LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getStackTrace() );
									}
									
									// inform listeners
									for( int i = 0; i < this.jotrListeners.size(); i++ ) {
										this.jotrListeners.get( i ).dhKeyMessageRequested( context );
										this.jotrListeners.get( i ).authentificationStarted( context );
									}
									
									break;
									
								//
								// If authstate is AUTHSTATE_AWAITING_DHKEY:
								// 		This is the trickiest transition in the whole protocol. 
								//		It indicates that you have already sent a D-H Commit message 
								//		to your correspondent, but that he either didn't receive it, or 
								//		just didn't receive it yet, and has sent you one as well. The 
								//		symmetry will be broken by comparing the hashed gx you sent in 
								//		your D-H Commit Message with the one you received, considered as 
								//		32-byte unsigned big-endian values.
								//
								//  	If yours is the higher hash value:
								//			Ignore the incoming D-H Commit message, but resend your D-H Commit message.
								//			Otherwise:
								//				Forget your old gx value that you sent (encrypted) earlier, 
								//				and pretend you're in AUTHSTATE_NONE; i.e. reply with a D-H Key 
								//				Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.
								//
								case JotrContext.AUTHSTATE_AWAITING_DHKEY:
									
									// create integer values
									BigInteger gy_int = new BigInteger( context.getRemotePublicKeyHash() );  
									BigInteger gx_int = new BigInteger( context.getLocalPublicKeyHash() );
									
									// if gx_int is higher
									if( gy_int.abs().compareTo( gx_int.abs()  ) == -1 ) {
										for( int i = 0; i < this.jotrListeners.size(); i++ )
											this.jotrListeners.get( i ).dhCommitMessageRequested( context );

									// otherwise
									} else {
										context.setRemoteAesEncryptedPublicKey( gy_byteArray );
										context.setRemotePublicKeyHash( gy_hash );
										
										context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_REVEALSIG );
										
										for( int i = 0; i < this.jotrListeners.size(); i++ )
											this.jotrListeners.get( i ).dhKeyMessageRequested( context );
									}

									break;
									
								// If authstate is AUTHSTATE_AWAITING_REVEALSIG:
								// 		Retransmit your D-H Key Message (the same one as you sent when you 
								//		entered AUTHSTATE_AWAITING_REVEALSIG). Forget the old D-H Commit message, 
								//		and use this new one instead. There are a number of reasons this might
								//		happen, including:
								//
								//			* Your correspondent simply started a new AKE.
								//			* Your correspondent resent his D-H Commit message, as specified above.
								//			* On some networks, like AIM, if your correspondent is logged in multiple 
								//			  times, each of his clients will send a D-H Commit Message in response 
								//			  to a Query Message; resending the same D-H Key Message in response to 
								//			  each of those messages will prevent compounded confusion, since each of 
								//			  his clients will see each of the D-H Key Messages you send. 
								//			  [And the problem gets even worse if you are each logged in multiple times.]
								//
								case JotrContext.AUTHSTATE_AWAITING_REVEALSIG:
									
									context.setRemoteAesEncryptedPublicKey( gy_byteArray );
									context.setRemotePublicKeyHash( gy_hash );
									
									context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_REVEALSIG );
									
									for( int i = 0; i < this.jotrListeners.size(); i++ )
										this.jotrListeners.get( i ).dhKeyMessageRequested( context );
									
									break;
									
								// If authstate is AUTHSTATE_AWAITING_SIG or AUTHSTATE_V1_SETUP:
								//		Reply with a new D-H Key message, and transition authstate to 
								//		AUTHSTATE_AWAITING_REVEALSIG. 
								case JotrContext.AUTHSTATE_AWAITING_SIG:
								case JotrContext.AUTHSTATE_V1_SETUP:
									
									context.setRemoteAesEncryptedPublicKey( gy_byteArray );
									context.setRemotePublicKeyHash( gy_hash );
									
									context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_REVEALSIG );
									
									for( int i = 0; i < this.jotrListeners.size(); i++ )
										this.jotrListeners.get( i ).dhKeyMessageRequested( context );
									
									break;
							}
						}
						return MESSAGE_IGNORE;
						
					// MESSAGE TYPE : D-H Key Message
					case MESSAGE_TYPE_DH_KEY_MSG:
						
						// increment array cursor
						cursor += BYTE_BYTE_LENGTH;
						
						LogFunctions.log( "Jotr.processIncomingMessage", "message type = MESSAGE_TYPE_DH_KEY_MSG " );
						
						// inform listeners
						for( int i = 0; i < this.jotrListeners.size(); i++ )
							this.jotrListeners.get( i ).dhKeyMessageReceived( context );
						
						//
						// If ALLOW_V2 is not set, ignore this message. Otherwise:
						//
						if( context.isAllowV2() ) {
							
							
							// get remote public key length and increment array cursor
							int dataLength = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
							cursor += INT_BYTE_LENGTH;
							
							// get remote public key and increment array cursor
							byte[] gy_byteArray = ArrayFunctions.seperateDataFromArray( cursor, dataLength,  bytes );
							cursor += dataLength;
							
							switch( context.getAuthstate() ) {
								// If authstate is AUTHSTATE_AWAITING_DHKEY:
								//		Reply with a Reveal Signature Message and transition authstate 
								//		to AUTHSTATE_AWAITING_SIG.
								case JotrContext.AUTHSTATE_AWAITING_DHKEY:

									// set remote public key
									jotrDh.setCurrentRemotePublicKey( gy_byteArray );
									
									// compute auth keys
									jotrDh.computeAKESharedSecretKeys();
									
									context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_SIG );

									// inform chat client
									for( int i = 0; i < this.jotrListeners.size(); i++ )
										this.jotrListeners.get( i ).dhRevealSignatureMessageRequested( context );

									break;
									
								// If auth state is AUTHSTATE_AWAITING_SIG:
								//
								//    If this D-H Key message is the same the one you received earlier 
								//	  (when you entered AUTHSTATE_AWAITING_SIG):
								//        Retransmit your Reveal Signature Message.
								//    Otherwise:
								//        Ignore the message. 
								case JotrContext.AUTHSTATE_AWAITING_SIG:
									
									if( jotrDh.getCurrentRemotePublicKey() != null  &&  gy_byteArray.equals( jotrDh.getCurrentRemotePublicKeyAsBytes() ) )
										for( int i = 0; i < this.jotrListeners.size(); i++ )
											this.jotrListeners.get( i ).dhRevealSignatureMessageRequested( context );										
									// TODO CHECK THIS IF
									break;							  
								
							}
						}
						return MESSAGE_IGNORE;
						
					case MESSAGE_TYPE_REVEAL_SIG_MSG:
						
						// increment array cursor
						cursor += BYTE_BYTE_LENGTH;
						
						LogFunctions.log( "Jotr.processIncomingMessage", "message type = MESSAGE_TYPE_REVEAL_SIG " );
						
						// inform listeners
						for( int i = 0; i < this.jotrListeners.size(); i++ )
							this.jotrListeners.get( i ).dhRevealSignatureMessageReceived( context );
						
						//
						// If ALLOW_V2 is not set or authstate is not AUTHSTATE_AWAITING_REVEALSIG, 
						// ignore this message. Otherwise:
						//	
						// 		Use the received value of r to decrypt the value of gx received in the D-H Commit Message, 
						// 		and verify the hash therein. Decrypt the encrypted signature, and verify the signature and 
						//		the MACs. If everything checks out:
						//
					    //    		* Reply with a Signature Message.
					    //    		* Transition authstate to AUTHSTATE_NONE.
					    //    		* Transition msgstate to MSGSTATE_ENCRYPTED.
					    //    		* If there is a recent stored message, encrypt it and send it as a Data Message.
						//
					    // Otherwise, ignore the message.
						//
						if( context.isAllowV2() && context.getAuthstate() == JotrContext.AUTHSTATE_AWAITING_REVEALSIG ) {

							// get aes key length and increment array cursor
							int dataLength = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, bytes );
							cursor += INT_BYTE_LENGTH;
							
							// get aes key and increment array cursor
							byte[] r = ArrayFunctions.seperateDataFromArray( cursor, dataLength,  bytes );
							context.setAesKey( r );
							cursor += dataLength;

							try {
								
								LogFunctions.log( "Jotr.processIncomingMessage", "decrypt d-h remote public key" );
								
								// ------------------------------------------------------------------------------------------------------
								// use r to decrypt the value of gx sent earliern.
								// ------------------------------------------------------------------------------------------------------
								
								// decrypt the encrypted remote public key mpi
								Cipher c = JotrFunctions.generateDecryptingAesCipher( context.getAesKey() );
								byte[] dec_gy_mpi = c.doFinal( context.getRemoteAesEncryptedPublicKey() );
								
								
								
								// get value from mpi
								dataLength = ( int )ArrayFunctions.getBigEndianValue( 0, INT_BYTE_LENGTH, dec_gy_mpi );
								byte[] dec_gy = ArrayFunctions.seperateDataFromArray( INT_BYTE_LENGTH, dataLength, dec_gy_mpi );
								
								// set remotePublicKey
								jotrDh.setCurrentRemotePublicKey( dec_gy );
								
								if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "remote aes key", StringFunctions.bytesToHex( r ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "dec_gy_mpi", StringFunctions.bytesToHex( dec_gy_mpi ) );
								}
								
								// ------------------------------------------------------------------------------------------------------
								// verifie that HASH(gx) matches the value sent earlier
								// ------------------------------------------------------------------------------------------------------

								// create sha256 for the decrypted remote public key mpi
								dec_gy_mpi = HashFunctions.sha256( dec_gy_mpi );
								
								// compare the two hash values
								if( ArrayFunctions.compare( dec_gy_mpi, context.getRemotePublicKeyHash() ) )  {
									
									LogFunctions.log( "Jotr.processIncomingMessage", "hash compare = TRUE" );
									
									// ------------------------------------------------------------------------------------------------------
									// - verifie that Bob's gx is a legal value (2 <= gx <= modulus-2)
									// ------------------------------------------------------------------------------------------------------
									JotrMpi currentRemotePubKey = jotrDh.getCurrentRemotePublicKey();
									if( currentRemotePubKey.getValueAsBigInteger().compareTo( BigInteger.ONE ) != 1 )
										return MESSAGE_ERROR;

									
									// ------------------------------------------------------------------------------------------------------
									// Decrypt the Encrypted signature (DATA)
									// ------------------------------------------------------------------------------------------------------
													
									// get encrypted signature and increment array cursor
									byte[] signature = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, bytes );
									cursor += INT_BYTE_LENGTH + signature.length ;

									// ------------------------------------------------------------------------------------------------------
									// - compute d-h shared secret ( s = (gx)y ) ( this is needed to decrypt the signature ) 
									// - compute two AES keys c, c' and four MAC keys m1, m1', m2, m2' by hashing s in various ways
									// ------------------------------------------------------------------------------------------------------
									LogFunctions.log( "Jotr.processIncomingMessage", "compute d-h shared secret, aes key c and two mac keys m1 and m2" );
									
									// compute auth keys
									jotrDh.computeAKESharedSecretKeys();
									
									// ------------------------------------------------------------------------------------------------------
									// use m2 to verify MACm2(AESc(XB))
									// ------------------------------------------------------------------------------------------------------
									// get hmac sha-256-160 and increment array cursor
									byte[] hmac = ArrayFunctions.seperateDataFromArray( cursor, bytes.length - cursor,  bytes );
									cursor += 20;
									
									// get auth keys
									JotrAuthKeys authKeys = jotrDh.getAuthKeys();
									
									// compute hmac to verify
									byte[] c_hmac = HashFunctions.hmacSha256160( authKeys.getM2Key(), ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, signature ) );
									
									if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
										LogFunctions.log( "Jotr.processIncomingMessage", "HMAC", StringFunctions.bytesToHex( hmac ) );
										LogFunctions.log( "Jotr.processIncomingMessage", "C_HMAC", StringFunctions.bytesToHex( c_hmac ) );	
									}
									
									// verify
									if( ArrayFunctions.compare( hmac, c_hmac )  ) {
										LogFunctions.log( "Jotr.processIncomingMessage", "reveal signature hmac compare = TRUE" );
										
										// ------------------------------------------------------------------------------------------------------
										// use c to decrypt AESc(XB) to obtain XB = pubB, keyidB, sigB(MB)
										// ------------------------------------------------------------------------------------------------------
										
										// decrypt signature
										c = JotrFunctions.generateDecryptingAesCipher( authKeys.getCKey() );
										byte[] xb = c.doFinal( signature );
										
										// verify signature
										if( verifySignature( context, xb, true ) ) {
											LogFunctions.log( "Jotr.processIncomingMessage", "reveal signature ok" );
											
											context.setAuthstate( JotrContext.AUTHSTATE_NONE );
											context.setMessageState( JotrContext.MSGSTATE_ENCRYPTED );
											
											context.setProtoVersion( PROTOCOL_VERSION_2 );
											
											// inform listeners
											for( int i = 0; i < this.jotrListeners.size(); i++ ) {
												this.jotrListeners.get( i ).dhSignatureMessageRequested( context );
												this.jotrListeners.get( i ).authentificationFinished( context );
											}
											
											// TODO check keyid's
											jotrDh.computeInitialSessionKeys();
											
										} else {											
											LogFunctions.log( "Jotr.processIncomingMessage", "reveal message failed" );
											// ignore this message
											return MESSAGE_IGNORE;
										}
										
									} else {
										LogFunctions.log( "Jotr.processIncomingMessage", "reveal signature hmac compare = FALSE" );
										return MESSAGE_ERROR;
									}
									
								} else {
									
									LogFunctions.log( "Jotr.processIncomingMessage", "hash compare = FALSE" );
									return MESSAGE_ERROR;
								}
								
							} catch ( Exception e ) {
								LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getMessage());
								LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getStackTrace() );
							}
						}
						return MESSAGE_IGNORE;
						
					case MESSAGE_TYPE_SIG_MSG:
						
						// increment array cursor
						cursor += BYTE_BYTE_LENGTH;
						
						LogFunctions.log( "Jotr.processIncomingMessage", "message type = MESSAGE_TYPE_SIG_MSG" );
						
						// inform listeners
						for( int i = 0; i < this.jotrListeners.size(); i++ )
							this.jotrListeners.get( i ).dhSignatureMessageReceived( context );
						
						//
						// If ALLOW_V2 is not set or authstate is not AUTHSTATE_AWAITING_SIG, 
						// ignore this message. Otherwise:
						//	
						// 		Decrypt the encrypted signature, and verify the signature and the MACs. If everything checks out:
						//
				        //			* Transition authstate to AUTHSTATE_NONE.
				        //			* Transition msgstate to MSGSTATE_ENCRYPTED.
				        //			* If there is a recent stored message, encrypt it and send it as a Data Message.
						//
					    // Otherwise, ignore the message.
						//
						if( context.isAllowV2() && context.getAuthstate() == JotrContext.AUTHSTATE_AWAITING_SIG ) {

							try {							
								// ------------------------------------------------------------------------------------------------------
								// Decrypt the Encrypted signature (DATA)
								// ------------------------------------------------------------------------------------------------------
											
								// get encrypted signature and increment array cursor
								byte[] signature = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, bytes );
								cursor += INT_BYTE_LENGTH + signature.length ;
							
							
								// ------------------------------------------------------------------------------------------------------
								// use m2i to verify MACm2i(AESci(XB))
								// ------------------------------------------------------------------------------------------------------
							
								// get hmac sha-256-160 and increment array cursor
								byte[] hmac = ArrayFunctions.seperateDataFromArray( cursor, bytes.length - cursor,  bytes );
								cursor += 20;
							
								// get auth keys
								JotrAuthKeys authKeys = jotrDh.getAuthKeys();
								
								// compute hmac to verify
								byte[] ci_hmac = HashFunctions.hmacSha256160( authKeys.getM2iKey(), ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, signature ) );
							
								if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "HMAC", StringFunctions.bytesToHex( hmac ) );
									LogFunctions.log( "Jotr.processIncomingMessage", "Ci_HMAC", StringFunctions.bytesToHex( ci_hmac ) );	
								}
								
								// verify
								if( ArrayFunctions.compare( hmac, ci_hmac )  ) {
									LogFunctions.log( "Jotr.processIncomingMessage", "signature hmac compare = TRUE" );
									
									// ------------------------------------------------------------------------------------------------------
									// use ci to decrypt AESci(XA) to obtain XA = pubA, keyidA, sigA(MA)
									// ------------------------------------------------------------------------------------------------------
									
									// decrypt signature
									Cipher c = JotrFunctions.generateDecryptingAesCipher( authKeys.getCiKey() );
									byte[] xa = c.doFinal( signature );
									
									// verify signature
									if( verifySignature( context, xa, false ) ) {
										LogFunctions.log( "Jotr.processIncomingMessage", "signature ok" );
										
										context.setAuthstate( JotrContext.AUTHSTATE_NONE );
										context.setMessageState( JotrContext.MSGSTATE_ENCRYPTED );
										
										context.setProtoVersion( PROTOCOL_VERSION_2 );
										
										// inform listeners
										for( int i = 0; i < this.jotrListeners.size(); i++ )
											this.jotrListeners.get( i ).authentificationFinished( context );
										
										// TODO check keyid's
										jotrDh.computeInitialSessionKeys();
										
									} else {											
										LogFunctions.log( "Jotr.processIncomingMessage", "sigature message failed" );
										// ignore this message
										return MESSAGE_IGNORE;
									}
									
								} else {
									LogFunctions.log( "Jotr.processIncomingMessage", "signature hmac compare = FALSE" );
									return MESSAGE_ERROR;
								}
								
							} catch ( Exception e ) {
								LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getMessage() );
								LogFunctions.log( "Jotr.processIncomingMessage" , "Exception", e.getStackTrace() );
							}
							
						}
						return MESSAGE_IGNORE;

						
				}
				return MESSAGE_ENCRYPTED;
			
			} else {
				return MESSAGE_ERROR;
			}
			
		} else {
			
			LogFunctions.log( "Jotr.processIncomingMessage", "plaintext available" );
			
			int result = JotrFunctions.checkForWhitespaceTag( message );
			
			// set message state
			context.setMessageState( JotrContext.MSGSTATE_PLAINTEXT );
			
			switch( result ) {
			
				case PROTOCOL_VERSION_1:

					LogFunctions.log( "Jotr.processIncomingMessage", "plaintext with otr whitespacetag v1" );

					processPlaintextMsgWithWhitespace( context, message );
					if( context.isWhitespaceStartAke() && context.isAllowV1() ) {
						context.setAuthstate( JotrContext.AUTHSTATE_V1_SETUP );
						// TODO v1 dh message
						//for( int i = 0; i < this.jotrListeners.size(); i++ )
//							//this.jotrListeners.get( i ).dhCommitMessageRequested( context );
					}
					break;
				case PROTOCOL_VERSION_2:
					
					LogFunctions.log( "Jotr.processIncomingMessage", "plaintext with otr whitespacetag v2" );
					
					processPlaintextMsgWithWhitespace( context, message );
					if( context.isWhitespaceStartAke() && context.isAllowV2() ) {
						context.setAuthstate( JotrContext.AUTHSTATE_AWAITING_DHKEY );
						for( int i = 0; i < this.jotrListeners.size(); i++ )
							this.jotrListeners.get( i ).dhCommitMessageRequested( context );
					}
					break;
				// process messages without a whitespace tag
				default:
					processPlaintextMsgWithoutWhitespace( context, message );
					break;
			}
			return MESSAGE_PLAIN;
		}
	}
	
	public void removeJotrListener( JotrListener listener ) {
		this.jotrListeners.remove( listener );
	}
	
	public void addJotrListener( JotrListener listener ) {
		this.jotrListeners.add( listener );
	}
	
	// #############################################################################
	// Helperfunctions
	// #############################################################################
	
	
	/**
	 * Creates a data message from the given data and context.
	 * 
	 * @param context The context which contains the otr data of the receiving contact.
	 * @param data The data to create the message from.
	 */
	private void createDataMessage( JotrContext context, StringBuffer data ) {
		
		try {
			// d-h stuff
			JotrDH jotrDh = context.getJotrDh();
			JotrSessionKeys sessionKeys = jotrDh.getSessKeys()[ 1 ][ 0 ];
			
			// get key pair
			JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
			JotrDHKeyPair curLocalKeys = jotrDh.getCurrentLocalDHKeyPair();
			
			// get data and reset string buffer
			byte[] dataToEncrypt = data.toString().getBytes(); 
			data.setLength( 0 );
		
			// create header information
			byte[] version;
			if( context.getProtoVersion() == PROTOCOL_VERSION_1 )
				version = ArrayFunctions.intToByteArray( SHORT_BYTE_LENGTH, PROTOCOL_VERSION_1 );
			else 
				version = ArrayFunctions.intToByteArray( SHORT_BYTE_LENGTH, PROTOCOL_VERSION_2 );
			
			// create message type and flags
			byte[] messageType = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, MESSAGE_TYPE_DATA_MSG );
			byte[] messageFlags = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, 0 );
		
			// create key id's
			byte[] senderKeyId = ArrayFunctions.intToByteArray( INT_BYTE_LENGTH, prevLocalKeys.getKeyID() );
			byte[] receiverKeyId = ArrayFunctions.intToByteArray( INT_BYTE_LENGTH, jotrDh.getCurrentRemotePublicKeyId() );
		
			byte[] nextDHpubKey = curLocalKeys.getPublicKey().getValueAsByteArray();
		
			// increment counter and create byte[] ctr
			context.incOurAesCounter();			
			byte[] ctr = ArrayFunctions.longToByteArray( CTR_BYTE_LENGTH, context.getOurAesCounter() );
		
			// encrypt data
			Cipher c = JotrFunctions.generateEncryptingAesCipher( sessionKeys.getSendingAesKey() , ctr );
			byte[] encryptedData = c.doFinal( dataToEncrypt );
			encryptedData = ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, encryptedData );
		
			// merge data for hmac
			byte[] ta = ArrayFunctions.merge( version,  messageType );
			ta = ArrayFunctions.merge( ta,  messageFlags );
			ta = ArrayFunctions.merge( ta,  senderKeyId );
			ta = ArrayFunctions.merge( ta,  receiverKeyId );
			ta = ArrayFunctions.merge( ta,  nextDHpubKey );
			ta = ArrayFunctions.merge( ta,  ctr );
			ta = ArrayFunctions.merge( ta,  encryptedData );
			
			// hmac data
			byte[] hmac = HashFunctions.hmacSha1( sessionKeys.getSendingMacKey(), ta );
						
			// create revealMacKeys
			byte[] revealMac = ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, jotrDh.getRevealedMacKeys() );
			
			// merge the data
			byte[] completeData = ArrayFunctions.merge( ta,  hmac );    
			completeData = ArrayFunctions.merge( completeData,  revealMac );
			
			if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
				LogFunctions.log( "Jotr.createDataMessage", 	"getSendingAesKey", StringFunctions.bytesToHex( sessionKeys.getSendingAesKey() ) );
				LogFunctions.log( "Jotr.createDataMessage", 	"sendingMacKey", 	StringFunctions.bytesToHex( sessionKeys.getSendingMacKey() ) );
				LogFunctions.log( "Jotr.createDataMessage", 	"ta", 				StringFunctions.bytesToHex( ta ) );
				LogFunctions.log( "Jotr.createDataMessage", 	"hmac", 			StringFunctions.bytesToHex( hmac ) );
				LogFunctions.log( "Jotr.createDataMessage", 	"completeData", 	StringFunctions.bytesToHex( completeData ) );
			}
			
			//pack otr message with header and footer
			JotrFunctions.generateOTRMessage( data, completeData );
		
		} catch ( Exception e ) {
			LogFunctions.log( "Jotr.createDataMessage" , "Exception", e.getMessage() );
			LogFunctions.log( "Jotr.createDataMessage" , "Exception", e.getStackTrace() );
		}
		
	}
	
	/**
	 * Verifies the given signature message.
	 * 
	 * @param context  The context which contains the otr data of the sending contact.
	 * @param signature The signature to verify.
	 * @param reveal Indicates if this message is a reveal signature.
	 * @return <b>true</b> if this signature is valid, <b>false</b> otherwise.
	 * 
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws SignatureException
	 */
	private boolean verifySignature( JotrContext context, byte[] signature, boolean reveal ) throws InvalidKeyException, 
																									NoSuchAlgorithmException, 
																									InvalidKeySpecException, 
																									SignatureException {
		
		// get D-H stuff
		JotrDH jotrDh = context.getJotrDh();
		JotrAuthKeys authKeys = jotrDh.getAuthKeys();
		JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
		
		// test key type ( if != 0 unknown )
		int cursor = 0;
		if(  ArrayFunctions.getBigEndianValue( cursor, SHORT_BYTE_LENGTH, signature ) != 0 )
			return false;
		
		cursor += SHORT_BYTE_LENGTH;

		// extract remote p, q, g, y
		byte[] remoteP = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, signature );
		cursor += ( INT_BYTE_LENGTH + remoteP.length );
		byte[] remoteQ = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, signature );
		cursor += ( INT_BYTE_LENGTH + remoteQ.length );
		byte[] remoteG = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, signature );
		cursor += ( INT_BYTE_LENGTH + remoteG.length );
		byte[] remoteY = ArrayFunctions.getNextPackage( cursor, INT_BYTE_LENGTH, signature );
		cursor += ( INT_BYTE_LENGTH + remoteY.length );
	
		// extract pubB
		byte[] pub = ArrayFunctions.seperateDataFromArray( 0, signature.length - ( 2 * remoteQ.length ) - 4, signature );
		
		// set keys if no key is available
		if( context.getDsaKeyStorage().getRemoteY() == null ) {
			context.getDsaKeyStorage().setRemoteP( new JotrMpi( remoteP ) );
			context.getDsaKeyStorage().setRemoteQ( new JotrMpi( remoteQ ) );
			context.getDsaKeyStorage().setRemoteG( new JotrMpi( remoteG ) );
			context.getDsaKeyStorage().setRemoteY( new JotrMpi( remoteY ) );
		
		// compare fingerprint
		} else {
			// TODO implement fingerprint stuff
			// calc fingerprint
			byte[] fingerprint = ArrayFunctions.seperateDataFromArray( 2, pub.length-2, pub );
			fingerprint = HashFunctions.sha1( fingerprint );
			
			if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 )
				LogFunctions.log( "Jotr.verifySignature", "fingerprint", StringFunctions.bytesToHex( fingerprint ) );
		}
												
		// extract keyid
		int keyid = ( int )ArrayFunctions.getBigEndianValue( cursor, INT_BYTE_LENGTH, signature );
		cursor += INT_BYTE_LENGTH;
		jotrDh.setCurrentRemotePublicKeyId( keyid );
		
		// extract sig
		byte[] sig = ArrayFunctions.seperateDataFromArray( cursor, signature.length - cursor, signature );
		
		// get r and s from signature
        byte[] sigR = ArrayFunctions.seperateDataFromArray( 0 , 20 , sig );
		byte[] sigS = ArrayFunctions.seperateDataFromArray( 20 , 20 , sig );
		
		if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
			LogFunctions.log( "Jotr.verifySignature", 	"authSig",			StringFunctions.bytesToHex( signature ) );
			LogFunctions.log( "Jotr.verifySignature", 	"remoteP", 			StringFunctions.bytesToHex( remoteP ) );
			LogFunctions.log( "Jotr.verifySignature", 	"remoteQ length", 	StringFunctions.bytesToHex( remoteQ ) );
			LogFunctions.log( "Jotr.verifySignature", 	"remoteG length", 	StringFunctions.bytesToHex( remoteG ) );
			LogFunctions.log( "Jotr.verifySignature", 	"remoteY length", 	StringFunctions.bytesToHex( remoteY ) );
			LogFunctions.log( "Jotr.verifySignature", 	"pubB", 			StringFunctions.bytesToHex( pub ) );
			LogFunctions.log( "Jotr.verifySignature", 	"keyidB", 			keyid );
			LogFunctions.log( "Jotr.verifySignature", 	"sigB", 			StringFunctions.bytesToHex( sig ) );
			LogFunctions.log( "Jotr.verifySignature", 	"sigB r", 			StringFunctions.bytesToHex( sigR ) );
			LogFunctions.log( "Jotr.verifySignature", 	"sigB s", 			StringFunctions.bytesToHex( sigS ) );
			LogFunctions.log( "Jotr.verifySignature", 	"theire pubkey", 	StringFunctions.bytesToHex( jotrDh.getCurrentRemotePublicKeyAsBytes() ) );
			LogFunctions.log( "Jotr.verifySignature", 	"our pubkey", 		StringFunctions.bytesToHex( prevLocalKeys.getPublicKey().getValueAsByteArray() ) );
		}
		
		// ------------------------------------------------------------------------------------------------------										
		// compute MA oder MB = MACm1(gx, gy, pub, keyid) ( depends on reveal or normal signature message )
		// ------------------------------------------------------------------------------------------------------				
		byte[] m = ArrayFunctions.merge( jotrDh.getCurrentRemotePublicKeyAsBytes(), prevLocalKeys.getPublicKey().getValueAsByteArray() );
		m = ArrayFunctions.merge( m, pub );
		m = ArrayFunctions.merge( m, ArrayFunctions.intToByteArray( INT_BYTE_LENGTH, keyid ) );
		
		if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) 
			LogFunctions.log( "Jotr.processIncomingMessage", "M", StringFunctions.bytesToHex( m ) );
		
		// hash m
		if( reveal )
			m = HashFunctions.hmacSha256( authKeys.getM1Key(), m );
		else 
			m = HashFunctions.hmacSha256( authKeys.getM1iKey(), m );
		
		if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) {
			LogFunctions.log( "Jotr.processIncomingMessage", "M hashed", 	StringFunctions.bytesToHex( m ) );
			LogFunctions.log( "Jotr.processIncomingMessage", "macKeyM1", 	StringFunctions.bytesToHex( authKeys.getM1Key() ) );
		}

		if( JotrFunctions.verifyDSASig( context , m, sigR, sigS ) )
			return true;
       

		return false;
		
	}

	/**
	 * Creates a valid signature message.
	 * 
	 * @param context The context which contains the otr data of the receiving contact.
	 * @param message The message append the signature to.
	 * @param reveal Indicates if this message is a reveal signature.
	 */
	private void generateSignatureMessage( JotrContext context, StringBuffer message, boolean reveal ) {
		LogFunctions.log( "Jotr.generateSignatureMessage", "start", "initial log message" );
		
		// get D-H stuff
		JotrDH jotrDh = context.getJotrDh();
		JotrAuthKeys authKeys = jotrDh.getAuthKeys();
		JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
		
		/// get dsa key storage
		JotrDsaKeyStorage storage = context.getDsaKeyStorage();
		
		try {
			// 	create header information
			byte[] version = ArrayFunctions.intToByteArray( SHORT_BYTE_LENGTH, PROTOCOL_VERSION_2 );
			
			byte[] messageType;
			if( reveal )
				messageType = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, MESSAGE_TYPE_REVEAL_SIG_MSG );
			else
				messageType = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, MESSAGE_TYPE_SIG_MSG );
				
				
			// create pubKey from and pubKeyType ( e.g. 0x00 0x00 )	p,q,g,y
			byte[] pubKey = new byte[] { ( byte )0x00, ( byte )0x00 };
			pubKey = ArrayFunctions.merge( pubKey, storage.getP().getValueAsByteArray() );
			pubKey = ArrayFunctions.merge( pubKey, storage.getQ().getValueAsByteArray() );
			pubKey = ArrayFunctions.merge( pubKey, storage.getG().getValueAsByteArray() );
			pubKey = ArrayFunctions.merge( pubKey, storage.getY().getValueAsByteArray() );
			
			// generate key id ( normally 1 in auth context )
			byte[] keyid = ArrayFunctions.intToByteArray( INT_BYTE_LENGTH, prevLocalKeys.getKeyID() );
			
			// create m
			byte[] m = ArrayFunctions.merge( prevLocalKeys.getPublicKey().getValueAsByteArray(), jotrDh.getCurrentRemotePublicKeyAsBytes() );
				
			m = ArrayFunctions.merge( m, pubKey );
			m = ArrayFunctions.merge( m, keyid );
			
			if( reveal )
				m = HashFunctions.hmacSha256( authKeys.getM1Key(), m );
			else 
				m = HashFunctions.hmacSha256( authKeys.getM1iKey(), m );
			
			// sign
			byte[] sig = JotrFunctions.signWithDSASig( context, m );
			
			if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 ) { 
				LogFunctions.log( "Jotr.generateSignatureMessage" , "m", 				StringFunctions.bytesToHex( m ) );
				LogFunctions.log( "Jotr.generateSignatureMessage" , "sig", 				StringFunctions.bytesToHex( sig ) );
				LogFunctions.log( "Jotr.generateSignatureMessage" , "sig.length", 		sig.length );
				LogFunctions.log( "Jotr.generateSignatureMessage", 	"theire pubkey",	StringFunctions.bytesToHex( jotrDh.getCurrentRemotePublicKeyAsBytes() ) );
				LogFunctions.log( "Jotr.generateSignatureMessage", 	"our pubkey", 		StringFunctions.bytesToHex( prevLocalKeys.getPublicKey().getValueAsByteArray() ) );
			}
			
			// merge signature message
			byte[] x = ArrayFunctions.merge( pubKey, keyid );
			x = ArrayFunctions.merge( x, sig );

			if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 )
				LogFunctions.log( "Jotr.generateSignatureMessage", 	"signature message",	StringFunctions.bytesToHex( x ) );
			
			// cipher the signature message
			Cipher c;
			if( reveal )
				c = JotrFunctions.generateEncryptingAesCipher( authKeys.getCKey() );
			else
				c = JotrFunctions.generateEncryptingAesCipher( authKeys.getCiKey() );
			
			x = c.doFinal( x );
			x = ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, x );
			
			// merge complete data
			byte[] completeData = ArrayFunctions.merge( version,  messageType );
			
			// append encryption key
			if( reveal )
				completeData = ArrayFunctions.merge( completeData, ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, context.getAesKey() ) );
			
			completeData = ArrayFunctions.merge( completeData, x );
			
			if( reveal )
				completeData = ArrayFunctions.merge( completeData, HashFunctions.hmacSha256160( authKeys.getM2Key(), x ) );
			else
				completeData = ArrayFunctions.merge( completeData, HashFunctions.hmacSha256160( authKeys.getM2iKey(), x ) );
		
			if( LogFunctions.logLevel > LogFunctions.LOG_LEVEL_1 )
				LogFunctions.log( "Jotr.generateSignatureMessage", 	"completeData",	StringFunctions.bytesToHex( completeData ) );
			
			// pack otr message with header and footer
			JotrFunctions.generateOTRMessage( message, completeData );
			
		} catch ( Exception e) {
			LogFunctions.log( "Jotr.generateSignatureMessage" , "Exception", e.getMessage() );
			LogFunctions.log( "Jotr.generateSignatureMessage" , "Exception", e.getStackTrace() );
		}
		
	}
	
	/**
	 * Generates the Diffie-Hellman Key Message. Result is stored in the StringBuffer object.
	 * 
	 * @param context The encryption informations.
	 * @param message StringBuffer object to store the result in.
	 */
	private void generateDHKeyMessage( JotrContext context, StringBuffer message ) {

		LogFunctions.log( "Jotr.generateDHKeyMessage", "start", "initial log message" );

		// get D-H stuff
		JotrDH jotrDh = context.getJotrDh();
		JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
		
		try {
			
			// create header information
			byte[] version = ArrayFunctions.intToByteArray( SHORT_BYTE_LENGTH, PROTOCOL_VERSION_2 );
			byte[] messageType = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, MESSAGE_TYPE_DH_KEY_MSG );
			
			// get gx
			byte[] gx_byteArray = prevLocalKeys.getPublicKey().getValueAsByteArray();
			LogFunctions.log( "Jotr.generateDHKeyMessage", "gx_byteArray", StringFunctions.bytesToHex( gx_byteArray ) );
			
			
			// merge array
			byte[] completeData = ArrayFunctions.merge( version, messageType );
			completeData = ArrayFunctions.merge( completeData, gx_byteArray );
			
			// pack otr message with header and footer
			JotrFunctions.generateOTRMessage( message, completeData );
			
		} catch ( Exception e) {
			LogFunctions.log( "Jotr.generateDHKeyMessage" , "Exception", e.getMessage() );
			LogFunctions.log( "Jotr.generateDHKeyMessage" , "Exception", e.getStackTrace() );
		}
	}
	
	/**
	 * Generates the Diffie-Hellman Commit Message. Result is stored in the StringBuffer object.
	 * 
	 * @param context The encryption informations.
	 * @param message StringBuffer object to store the result in.
	 */
	private void generateDHCommitMessage( JotrContext context, StringBuffer message ) {

		LogFunctions.log( "Jotr.generateDHCommitMessage", "start", "initial log message" );
		
		// get D-H stuff
		JotrDH jotrDh = context.getJotrDh();
		JotrDHKeyPair prevLocalKeys = jotrDh.getPreviousLocalDHKeyPair();
		
		try {

			// create header information
			byte[] version = ArrayFunctions.intToByteArray( SHORT_BYTE_LENGTH, PROTOCOL_VERSION_2 );
			byte[] messageType = ArrayFunctions.intToByteArray( BYTE_BYTE_LENGTH, MESSAGE_TYPE_DH_COMMIT_MSG );
		
			// get gx
			byte[] gx_byteArray = prevLocalKeys.getPublicKey().getValueAsByteArray();

			// create gx hash
			if( context.getLocalPublicKeyHash() == null )
				context.setLocalPublicKeyHash( HashFunctions.sha256( gx_byteArray ) );
			
			// create aes 128 ctr key
			if( context.getAesKey() == null ) {
				context.setAesKey( ArrayFunctions.generateRandomByteArray( JotrContext.BYTE_COUNT_AES_KEY ) );
			}
			
			LogFunctions.log( "Jotr.generateDHCommitMessage", "otr", "encrypt d-h local public key" );
			
			// cipher and pack gx
			Cipher c = JotrFunctions.generateEncryptingAesCipher( context.getAesKey() );
			byte[] enc_gx = ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, c.doFinal( gx_byteArray ) );

			
			// merge array
			byte[] completeData = ArrayFunctions.merge( version, messageType );
			completeData = ArrayFunctions.merge( completeData, enc_gx );
			completeData = ArrayFunctions.merge( completeData, ArrayFunctions.addBigEndianLengthHeader( INT_BYTE_LENGTH, context.getLocalPublicKeyHash() ) );
			
			// pack otr message with header and footer
			JotrFunctions.generateOTRMessage( message, completeData );
			
		} catch( Exception e ) {
			LogFunctions.log( "Jotr.generateDHCommitMessage" , "Exception", e.getMessage() );
			LogFunctions.log( "Jotr.generateDHCommitMessage" , "Exception", e.getStackTrace() );
		}
	}	
	
	/**
	 * Processes a message without whitespace-tag.<br><br>
	 * 
	 * If msgstate is MSGSTATE_PLAINTEXT: <br>
	 *  - Simply display the message to the user. <br>
	 *  - If REQUIRE_ENCRYPTION is set, warn him that the message was received unencrypted.<br>
	 * If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:<br>
     *	- Display the message to the user, but warn him that the message was received unencrypted.<br><br>
     *
     * If removeWhitespace is true, remove all whitespace-tags.<br><br>
     * 
     * Warnings are processed via JotrListener.<br><br>
	 * 
	 * @param context
	 * @param message The message to process.
	 * @param removeWhitespace
	 */
	private void processPlaintextMsg( JotrContext context, StringBuffer message, boolean removeWhitespace ) {
		
		if( removeWhitespace ) JotrFunctions.removeWhitespaceTags( message );
		
		switch( context.getMessageState() ) {
		
			case JotrContext.MSGSTATE_ENCRYPTED:
			case JotrContext.MSGSTATE_FINISHED:
				for( int i = 0; i < this.jotrListeners.size(); i++ )
					this.jotrListeners.get( i ).messageWasReceivedUnencrypted( context, message.toString() );
				break;
			
			case JotrContext.MSGSTATE_PLAINTEXT:
				if( context.isRequireEncryption() )
					for( int i = 0; i < this.jotrListeners.size(); i++ )
						this.jotrListeners.get( i ).messageWasReceivedUnencrypted( context, message.toString() );
				break;
		}
	}
	
	/**
	 * Processes a message with whitespace-tag.<br><br>
	 * 
	 * If msgstate is MSGSTATE_PLAINTEXT:<br>
     *	- Remove the whitespace tag and display the message to the user.<br>
     *    If REQUIRE_ENCRYPTION is set, warn him that the message was received unencrypted.<br>
	 * If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:<br>
     * 	- Remove the whitespace tag and display the message to the user, but warn him that the<br>
     *    message was received unencrypted.<br><br>
     *
     * Warnings are processed via JotrListener.<br><br>
	 * 
	 * @param context The context for this message.
	 * @param message The message to process.
	 */
	private void processPlaintextMsgWithWhitespace( JotrContext context,  StringBuffer message ) {
		processPlaintextMsg( context, message, true );
	}
	
	/**
	 * Processes a message without whitespace-tag.<br><br>
	 * 
	 * If msgstate is MSGSTATE_PLAINTEXT: <br>
	 *  - Simply display the message to the user. <br>
	 *  - If REQUIRE_ENCRYPTION is set, warn him that the message was received unencrypted.<br>
	 * If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:<br>
     *	- Display the message to the user, but warn him that the message was received unencrypted.<br><br>
     *
     * Warnings are processed via JotrListener.<br><br>
	 * 
	 * @param context The context for this message.
	 * @param message The message to process.
	 */
	private void processPlaintextMsgWithoutWhitespace( JotrContext context,  StringBuffer message ) {
		processPlaintextMsg( context, message, false );
	}

}
